home *** CD-ROM | disk | FTP | other *** search
/ Cream of the Crop 12 / Cream of the Crop 12 (Part II) / Cream of the Crop 12 (Part II).iso / OS2 / RXMIR12.ZIP / rxmirror.cmd < prev    next >
Encoding:
Text File  |  1996-03-07  |  23.2 KB  |  745 lines

  1. /* 
  2. rxmirror v1.2         Copyright (C) A.S.V. 1995,1996          FREEWARE
  3.  
  4. read     "rxmirror.doc" for details
  5. study    "license.doc" for legal stuff
  6. check    "ftp.sai.msu.su:/pub/os2/internet/ftp/" for new versions
  7. mail to  "asv@sai.msu.su" if you have questions or want to report bugs
  8. finger   rxmirror@crydee.sai.msu.su to get latest information
  9.  
  10. 02 Mar 1996
  11. */
  12.  
  13.    say
  14.    say "RxMirror  Version 1.2"
  15.    say "Copyright (C) A.S.V. 1995, 1996. This program is freeware"
  16.    say
  17.    
  18.    /* Loading necessary dlls */
  19.  
  20.    if RxFuncQuery("FtpLoadFuncs") then do
  21.       say "loading RxFtp extensions..."
  22.       call RxFuncAdd "FtpLoadFuncs","RxFtp","FtpLoadFuncs"
  23.       if result \= "0" then do
  24.          say "error loading RxFtp.dll"
  25.          exit
  26.          end
  27.       call FtpLoadFuncs "Quiet"
  28.    end
  29.  
  30.    if RxFuncQuery("SysLoadFuncs") then do
  31.       say "loading RexxUtil extensions..."
  32.       call RxFuncAdd "SysLoadFuncs","RexxUtil","SysLoadFuncs"
  33.       if result \= "0" then do
  34.          say "error loading RexxUtil.dll"
  35.          exit
  36.          end
  37.       call SysLoadFuncs
  38.    end
  39.  
  40.    if RxFuncQuery("Rx2LoadFuncs") then do
  41.       say "loading Rx2Util extensions..."
  42.       call RxFuncAdd "Rx2LoadFuncs","Rx2Util","Rx2LoadFuncs"
  43.       if result \= "0" then do
  44.          say "error loading Rx2Util.dll"
  45.          exit
  46.          end
  47.       call Rx2LoadFuncs "Quiet"
  48.    end
  49.    
  50.    /* setting up options */
  51.  
  52.    opt.user = "anonymous"
  53.    opt.passwd = ""   
  54.    opt.serverhost = ""
  55.    opt.startdir = ""
  56.    opt.mainlog  = directory()"/.rxmirror.mainlog"
  57.    opt.primarylog = directory()"/.rxmirror.files"
  58.    opt.loclog = ".rxmirror.log"
  59.    opt.inlog  = ".rxmirror.in"
  60.    opt.outlog = ".rxmirror.out"
  61.    opt.limit.Kbytes = 25000
  62.    opt.limit.nfiles = 300
  63.    opt.recovery = 0
  64.    opt.crashdir = ""
  65.    opt.loglvl = ""
  66.    opt.skip = ".rxmirror.skip"
  67.    opt.reflect = ".rxmirror.reflect"
  68.    opt.ignoretimestamp = 1
  69.    opt.sunserver = 0
  70.    opt.trueremove = 0
  71.  
  72.    do forever
  73.       lin = linein(".rxmirror.options")
  74.       if lin = "" then leave
  75.       if left(lin,1) = ";" then iterate
  76.       lin = strip(lin)
  77.       say "assigning option :" lin
  78.       interpret "opt."lin
  79.    end
  80.    call stream ".rxmirror.options", "C", "CLOSE"
  81.    say
  82.  
  83.    /* Handling arguments */
  84.    
  85.    parse arg host dir userid password .
  86.  
  87.    if host     \= "" then opt.serverhost = host
  88.    if dir      \= "" then opt.startdir = dir
  89.    if userid   \= "" then opt.user = userid
  90.    if password \= "" then opt.passwd = password
  91.  
  92.    if opt.serverhost = "" then do
  93.       say "server to mirror is not defined"
  94.       call Usage
  95.       exit
  96.    end
  97.  
  98.    if opt.startdir = "" then do
  99.       say "directory to mirror is not defined"
  100.       call Usage
  101.       exit
  102.    end
  103.  
  104.    if opt.passwd = "" then do
  105.       say "password is not defined"
  106.       call usage
  107.       exit
  108.    end
  109.  
  110.    say "mirroring directory :" opt.startdir
  111.    say "from server         :" opt.serverhost
  112.    say "userid              :" opt.user
  113.    say "password            :" opt.passwd
  114.    say
  115.  
  116.    if opt.passwd = "*" then do
  117.       say "Enter your password for" opt.user"@"opt.serverhost
  118.       say "(will not appear when typed, empty line will abort) :"
  119.       parse pull opt.passwd
  120.       say ''
  121.       if opt.passwd = "" then exit
  122.    end
  123.    
  124.    /* initialize statistics */
  125.  
  126.    glob.fdl = 0
  127.    glob.frm = 0
  128.    glob.bdl = 0
  129.    glob.brm = 0
  130.  
  131.    /* attempting crash recovery if necessary */
  132.  
  133.    signal off notready
  134.    if opt.recovery then do
  135.      say "attempting to recover from stopped transfer..."
  136.      do forever
  137.        lin = linein(opt.mainlog)
  138.        if substr(lin,24,3) = "dir" then do
  139.           completed = 0
  140.           crash = substr(lin,36)
  141.        end
  142.        else if substr(lin,24,3) = ">>>" then completed = 1
  143.        else if lin = "" then leave
  144.      end
  145.      opt.recovery = 0
  146.      if completed = 0 then do
  147.        say "crash condition detected; last directory is:"
  148.        say crash
  149.        opt.crashdir = crash
  150.        opt.recovery = 1
  151.      end
  152.    end
  153.  
  154.    /* Logging into remote host */
  155.    
  156.    if pos("M", opt.loglvl) \=0 then do
  157.      call logwrite opt.mainlog "<<<<<<<<<---------------------------------------------"
  158.      call logwrite opt.mainlog "host      :" opt.serverhost
  159.    end
  160.    
  161.    call FtpSetUser opt.serverhost, opt.user, opt.passwd
  162.    if result \= 1 then call perror "bad userid/password"
  163.    do forever
  164.       say "trying to login to '"opt.serverhost"'..."
  165.       call FtpPwd remotedir
  166.       if result = 0 then leave
  167.       if ftperrno = "FTPLOGIN" then do
  168.          say "server is busy; waiting for 5 minutes"
  169.          call SysSleep 60*5
  170.          iterate
  171.       end
  172.       call perror "login failed"
  173.    end
  174.    
  175.    call processdir opt.startdir
  176.    
  177.    if pos("M", opt.loglvl) \=0 then do
  178.      call logwrite opt.mainlog "IN  " right(glob.fdl,5) "file(s)," right(nicenum(glob.bdl),11) "bytes"
  179.      call logwrite opt.mainlog "OUT " right(glob.frm,5) "file(s)," right(nicenum(glob.brm),11) "bytes"
  180.      call logwrite opt.mainlog ">>>>>>>>>--------------------------------------------"
  181.    end
  182.    say
  183.    say "Totals :"
  184.    say "IN  " right(glob.fdl,5) "file(s)," right(nicenum(glob.bdl),11) "bytes"
  185.    say "OUT " right(glob.frm,5) "file(s)," right(nicenum(glob.brm),11) "bytes"
  186.    call FTPLogoff
  187.    exit
  188.    
  189. /* ==========================================================================
  190. processdir:
  191. performes mirroring for specified directory and subdirectories
  192. */
  193.    
  194.    processdir: procedure expose opt. glob. ftperrno
  195.    parse arg dir0
  196.  
  197.    /* set up current directory */
  198.    
  199.    say
  200.    say "heading to" dir0
  201.    call FtpChDir dir0
  202.    if result \= 0 then do
  203.       say "*** warning : cannot chdir to" dir0
  204.       if pos("M", opt.loglvl) \=0 then
  205.          call logwrite opt.mainlog "!cannot chdir to '"dir0"'"
  206.       if ftperrno \= "FTPCOMMAND" then
  207.          call perror "cannot do chdir to" dir0
  208.       return
  209.       end
  210.    call FtpPwd remotedir
  211.  
  212.    /* process subdirs */
  213.  
  214.    call getskiplist
  215.    call FTPLs "-la .", "file."
  216.    if result \= 0 then call perror "error getting remote file list"
  217.    cyear = substr(date('S'), 1, 4)
  218.    cmonth = substr(date('S'), 5, 2)
  219.    rnd=0
  220.    rnf=0
  221.    rsf=0
  222.    drop remdir.
  223.    drop remote.
  224.    do i=1 to file.0
  225.       if opt.sunserver = 0 then do
  226.          parse value file.i with rflags . . . rlen rmonth rday rtime rname
  227.          end
  228.       else do
  229.          parse value file.i with rflags . . rlen rmonth rday rtime rname
  230.          end
  231.       rname = strip(rname)
  232.       if isdir(rlen rflags rname) then do
  233.         rnd = rnd + 1
  234.         remdir.rnd.name = rname
  235.         remdir.rnd.len = rlen
  236.         remdir.rnd.flags = rflags
  237.       end
  238.       if rworth(rlen rflags rname) then do
  239.         rnf = rnf + 1
  240.         rsf = rsf + rlen
  241.         rmonth = month2dec(rmonth)
  242.         if pos(":", rtime) \= 0 then
  243.            if rmonth > cmonth then ryear = cyear-1
  244.            else                    ryear = cyear
  245.         else do
  246.            ryear = rtime
  247.            rtime = "00:00"
  248.         end
  249.         remote.rnf.name = rname
  250.         remote.rnf.len  = rlen
  251.         remote.rnf.flags = rflags
  252.         remote.rnf.year  = ryear
  253.         remote.rnf.month = rmonth
  254.         remote.rnf.day   = rday
  255.         remote.rnf.time  = rtime
  256.       end
  257.    end
  258.    drop file.
  259.  
  260.    /* check error recovery conditions */
  261.  
  262.    skipto = ""
  263.    if opt.recovery then do
  264.       if left(opt.crashdir,length(dir0)) \= dir0 then
  265.          call perror "illegal crash recovery attempt;" opt.crashdir dir0
  266.       skipto = substr(opt.crashdir,length(dir0)+2)
  267.       sp = pos("/",skipto)
  268.       if sp \= 0 then skipto = left(skipto,sp-1)
  269.       say "skipping to '"skipto"'"
  270.    end
  271.  
  272.    /* process this directory */
  273.  
  274.    call dirmirror dir0
  275.  
  276.    /* process subdirs */
  277.  
  278.    do i=1 to rnd
  279.       if \opt.recovery then do
  280.         call chkdirectory remdir.i.name
  281.         call directory remdir.i.name
  282.         if result = "" then iterate i
  283.         call processdir dir0'/'remdir.i.name
  284.         call directory ".."
  285.      end
  286.      else do
  287.         if remdir.i.name = skipto then do
  288.            call chkdirectory remdir.i.name
  289.            call directory remdir.i.name
  290.            if result = "" then iterate i
  291.            call processdir dir0'/'remdir.i.name
  292.            call directory ".."
  293.         end
  294.      end
  295.    end i
  296.  
  297.    if sp = 0 then opt.recovery = 0
  298.  
  299.    return
  300.    
  301. /*
  302. ===========================================================================
  303. ===========================================================================
  304. Main procedure: 
  305. mirroring of current dir on remote into current dir on local 
  306. */
  307.    
  308.    dirmirror: procedure expose ftperrno remote. rnf rsf opt. glob.
  309.    parse arg dir
  310.    
  311.    call FtpSetBinary "Binary"
  312.    if pos("M", opt.loglvl) \=0 then
  313.       call logwrite opt.mainlog "directory :" dir
  314.    
  315.    /* Build file lists */
  316.    
  317.    call getlocalfilelist
  318.    call getreflections
  319.    say right(rnf,5)      "remote files ("nicenum(rsf)" bytes)" 
  320.    say right(lnf,5)      "local files  ("nicenum(lsf) "bytes)"
  321.    if lnfr <> lnf then
  322.    say right(lnfr-lnf,5) "reflections  ("nicenum(lsfr-lsf) "bytes)"
  323.    
  324.    /* Check out if there's some file on remote and not on local disk */
  325.    
  326.    files_dl = 0
  327.    bytes_dl = 0
  328.    do i=1 to rnf
  329.      found=0
  330.      if lnfr \= 0 then
  331.      do j=1 to lnfr
  332.        if match(i j) then do; found=1; leave; end
  333.      end j
  334.      if \found then do
  335.        say "retrieving file '"remote.i.name"'," nicenum(remote.i.len) "bytes..."
  336.        call retrieve i
  337.        if result = 0 then do
  338.           files_dl = files_dl + 1
  339.           bytes_dl = bytes_dl + remote.i.len
  340.           if pos("F", opt.loglvl) \=0 then
  341.              call logwrite opt.inlog right(remote.i.len,10) remote.i.name
  342.           if pos("P", opt.loglvl) \=0 then
  343.              call logwrite opt.primarylog "+" right(remote.i.len,10) dir"/"remote.i.name
  344.        end
  345.      end
  346.      if files_dl > opt.limit.nfiles | bytes_dl/1024 > opt.limit.Kbytes then do
  347.        say "files/bytes limit exceeded, leaving"
  348.        if pos("M", opt.loglvl) \=0 then
  349.           call logwrite opt.mainlog "files/bytes limit exceeded, leaving"
  350.        leave i
  351.      end
  352.    end i
  353.    if files_dl \= 0 then do
  354.      say files_dl "file(s) retrieved from remote host"
  355.      say
  356.    end
  357.    
  358.    /* rebuild local list to accomodate changes */
  359.    
  360.    call getlocalfilelist
  361.    
  362.    /* Check out if there's some file on local disk and not on remote */
  363.    
  364.    files_rm = 0
  365.    bytes_rm = 0
  366.    remdircheck = 0
  367.    do j=1 to lnf
  368.      found=0
  369.      if rnf \= 0 then
  370.      do i=1 to rnf
  371.        if match(i j) then do; found=1; leave; end
  372.      end i
  373.      if \found then do
  374.        if \opt.trueremove then do
  375.           if \remdircheck then call chkdirectory ".removed"
  376.           say "removing local file '"local.j.name"' as obsolete..."
  377.           call SysFileDelete ".removed\"local.j.name
  378.           "@move" local.j.name ".removed\"local.j.name ">nul"
  379.           remdircheck = 1
  380.        end
  381.        else do
  382.           call SysFileDelete local.j.name
  383.        end
  384.        if pos("F", opt.loglvl) \=0 then
  385.          call logwrite opt.outlog right(local.j.len,10) local.j.name
  386.        if pos("P", opt.loglvl) \=0 then
  387.          call logwrite opt.primarylog "-" right(local.j.len,10) dir"/"local.j.name
  388.        files_rm = files_rm + 1
  389.        bytes_rm = bytes_rm + local.j.len
  390.      end
  391.    end j
  392.    if files_rm \= 0 then do
  393.      say files_rm "file(s) removed because they're not present on remote host"
  394.    end
  395.    
  396.    /* Quitting out of there */
  397.    
  398.    if files_dl \= 0 | bytes_dl \= 0 then do
  399.      if pos("L", opt.loglvl) \=0 then
  400.         call logwrite opt.loclog "IN  " right(files_dl,5) "file(s)," right(nicenum(bytes_dl),11) "bytes"
  401.      say "retrieved "files_dl" file(s), "nicenum(bytes_dl)" bytes"
  402.    end
  403.    if files_rm \= 0 | bytes_rm \= 0 then do
  404.      if pos("L", opt.loglvl) \=0 then
  405.         call logwrite opt.loclog "OUT " right(files_rm,5) "file(s)," right(nicenum(bytes_rm),11) "bytes"
  406.      say "removed  "files_rm" file(s), "nicenum(bytes_rm)" bytes"
  407.    end
  408.  
  409.    glob.fdl = glob.fdl + files_dl
  410.    glob.bdl = glob.bdl + bytes_dl
  411.    glob.frm = glob.frm + files_rm
  412.    glob.brm = glob.brm + bytes_rm
  413.  
  414.    Return   
  415.    
  416. /* ------------------------------------------------------------------------- */
  417.    
  418. /* determine whether remote file worth to mirror -------------------------- */
  419.    
  420.    rworth: procedure expose skp. nskp
  421.    parse arg len flags name
  422.    if name = ""                then return 0
  423.    if name = "index.html"      then return 0
  424.    if name = "index.htm"       then return 0
  425.    if left(name,1)  = "."      then return 0
  426.    if left(flags,1) \= "-"     then return 0
  427.    if len = "0"                then return 0
  428.    if length(name) < 1         then return 0
  429.    if words(flags) > 1         then return 0
  430.    if pos(".bad",name) \= 0    then return 0
  431.    if pos(".core",name) \= 0   then return 0
  432.    if pos(".try",name) \= 0    then return 0
  433.    if \isvalid(name)           then return 0
  434.    do i = 1 to nskp
  435.      if wildcardmatch(skp.i name) then return 0
  436.    end
  437.    return 1
  438.    
  439. /* determine whether local file worth to mirror ------------------------- */
  440.    
  441.    lworth: procedure
  442.    parse arg name len
  443.    if left(name,1)  = "."    then return 0
  444.    if len = "0"              then return 0
  445.    if length(name) < 1       then return 0
  446.    if \isvalid(name)         then return 0
  447.    return 1
  448.    
  449. /* do two files match ? --------------------------------------------------- */
  450.    
  451.    match: procedure expose remote. local. opt.
  452.    parse arg i j
  453.    if translate(strip(remote.i.name,"T",".")) \= translate(local.j.name) then return 0
  454.    if remote.i.len \= local.j.len  then return 0
  455.    if opt.ignoretimestamp then return 1
  456.    /* say "comparing["remotetimestamp(i)"]vs["local.j.date"]" */
  457.    if remotetimestamp(i) \= local.j.date then return 0
  458.    return 1
  459.    
  460. /* fetching file from remote ---------------------------------------------- */
  461.    
  462.    retrieve: procedure expose opt. remote.
  463.    parse arg index
  464.    filename = remote.index.name
  465.    call FtpGet filename, filename
  466.    if result \= 0 then do
  467.       say "*** warning: unable to download file '"filename"'; ftperrno is" ftperrno
  468.       if pos("M", opt.loglvl) \=0 then
  469.          call logwrite opt.mainlog "!unable to download '"filename"'"
  470.       if ftperrno \= "FTPCOMMAND" then
  471.          call perror "file fetching failed on" filename
  472.       ftperrno = ""
  473.       return 1
  474.       end
  475.    hour = substr(remote.index.time, 1, 2)
  476.    min = substr(remote.index.time, 4, 2)
  477.    call Rx2SetFileTimestamp filename, remote.index.day, remote.index.month, remote.index.year, hour, min, 0
  478.    if result \= 0 then do
  479.       say "unable to set timestamp for '"filename"'"
  480.    end
  481.    return 0
  482.    
  483. /* write a string to log file -------------------------------------------  */
  484.    
  485.    logwrite: procedure
  486.    parse arg logname str
  487.    call stream  logname, "C", "OPEN WRITE"
  488.    call stream  logname, "C", "SEEK <0"
  489.    call lineout logname, right(date(),11) time()' : 'str
  490.    call stream  logname, "C", "CLOSE"
  491.    return 1
  492.   
  493. /* process errors -------------------------------------------------------- */
  494.    
  495.    perror: procedure expose opt. ftperrno
  496.    parse arg errstr
  497.    say errstr ", ftp error code is" ftperrno
  498.    if pos("M", opt.loglvl) \=0 then
  499.       call logwrite opt.mainlog "ERR:" errstr", ftp error code is" ftperrno
  500.    exit
  501.    
  502. /* nice digit output ------------------------------------------------------ */
  503.    
  504.    nicenum: procedure
  505.    parse arg num
  506.    bil = num%1000000000
  507.    mil = num%1000000 - bil*1000
  508.    th  = num%1000 - mil*1000 - bil*1000000
  509.    ed  = num - th*1000 - mil*1000000 - bil*1000000000
  510.    out = ""
  511.    if bil \= 0 then out = out""bil","
  512.    if mil \= 0 then if bil \= 0 then out = out""right(mil,3,'0')","
  513.                     else             out = out""mil","
  514.    else             if bil \= 0 then out = out""right(mil,3,'0')","
  515.    if th  \= 0 then if bil \= 0 | mil \= 0 then out = out""right(th,3,'0')","
  516.                     else                        out = out""th","
  517.    else             if bil \= 0 | mil \= 0 then out = out""right(th,3,'0')","
  518.                     if bil \= 0 | mil \= 0 | th \= 0 then out = out""right(ed,3,'0')
  519.                     else                                  out = out""ed
  520.    return out
  521.    
  522. /* Getting local file list ----------------------------------------------- */
  523.    
  524.    getlocalfilelist: procedure expose local. lnf lnfr lsf lsfr
  525.    cutpos = length(directory())+2
  526.    Call SysFileTree "*", "file.", "F"
  527.    lnf=0
  528.    lsf=0
  529.    drop local.
  530.    do i=1 to file.0
  531.       parse value file.i with dt tm llen . lname
  532.       lname = substr(strip(lname), cutpos)
  533.       if lworth(lname llen) then do
  534.         lnf = lnf + 1
  535.         lsf = lsf + llen
  536.         local.lnf.name = lname
  537.         local.lnf.len = llen
  538.         local.lnf.date = maketimestamp(dt tm)
  539.       end
  540.    end
  541.    lnfr = lnf
  542.    lsfr = lsf
  543.    drop file.
  544.    return
  545.  
  546. /* Getting reflections list ---------------------------------------------- */
  547.    
  548.    getreflections: procedure expose local. opt. lnf lnfr lsf lsfr
  549.    lnfr=lnf
  550.    do forever
  551.       lin = linein(opt.reflect)
  552.       if lin = "" then leave
  553.       if left(lin,1) = ";" then iterate
  554.       lin = strip(strip(lin),"T","\")
  555.       say "reflecting " lin
  556.       Call SysFileTree lin"\*", "file.", "F"
  557.       do i=1 to file.0
  558.          parse value file.i with dt tm llen . lname
  559.          lname = filespec("NAME", lname)
  560.          if lworth(lname llen) then do
  561.            lnfr = lnfr + 1
  562.            lsfr = lsfr + llen
  563.            local.lnfr.name = lname
  564.            local.lnfr.len = llen
  565.            local.lnfr.date = maketimestamp(dt tm)
  566.          end
  567.       end
  568.       drop file.
  569.    end
  570.    return
  571.  
  572. /* Getting skiplist ----------------------------------------------------- */
  573.    
  574.    getskiplist: procedure expose opt. skp. nskp
  575.    nskp = 0
  576.    do forever
  577.       lin = linein(opt.skip)
  578.       if lin = "" then leave
  579.       if left(lin,1) = ";" then iterate
  580.       lin = strip(strip(lin),"T","\")
  581.       nskp = nskp + 1
  582.       skp.nskp = lin
  583.       say "added to skiplist: " skp.nskp
  584.    end
  585.    rc = stream(opt.skip, "C", "CLOSE")
  586.    return
  587.    
  588. /* Checking dir presence ------------------------------------------------ */
  589.    
  590.    chkdirectory: procedure
  591.    parse arg dirname
  592.    Call SysFileTree dirname, "dir.", "D"
  593.    if dir.0 = 0 then do
  594.      call SysMkDir dirname
  595.    end
  596.    return
  597.  
  598. /* determine is this directory or not -------------------------------------- */
  599.    
  600.    isdir: procedure expose skp. nskp
  601.    parse arg len flags name
  602.    if flags = ""               then return 0
  603.    if substr(name,1,1)  = "."  then return 0
  604.    if substr(flags,1,1) \= "d" then return 0
  605.    if words(flags) > 1         then return 0
  606.    if \isvalid(name)           then return 0
  607.    do i = 1 to nskp
  608.      if wildcardmatch(skp.i name) then return 0
  609.    end
  610.    return 1
  611.  
  612. /* determine is filename valid or not -------------------------------------- */
  613.  
  614.    isvalid: procedure
  615.    parse arg name
  616.    if name = "" then return 0
  617.    if words(name) > 1 then return 0
  618.    if verify(name,,
  619.       "!#$%&'()+,-.01234567890;=@"||,
  620.       "ABCDEFGHIJKLMNOPQRSTUVWXYZ[]_`"||,
  621.       "abcdefghijklmnopqrstuvwxyz{}~") \= 0 then return 0
  622.    return 1
  623.  
  624. /* month2dec: converts month in string representation into numeric form,
  625.    i.e. Nov -> 11 */
  626.  
  627.   month2dec: procedure
  628.   parse arg month
  629.   select
  630.      when month = "Jan" then return 1
  631.      when month = "Feb" then return 2
  632.      when month = "Mar" then return 3
  633.      when month = "Apr" then return 4
  634.      when month = "May" then return 5
  635.      when month = "Jun" then return 6
  636.      when month = "Jul" then return 7
  637.      when month = "Aug" then return 8
  638.      when month = "Sep" then return 9
  639.      when month = "Oct" then return 10
  640.      when month = "Nov" then return 11
  641.      when month = "Dec" then return 12
  642.      otherwise say "wrong month" month "; assuming November"
  643.   end /* select */
  644.   return 1
  645.  
  646. /* maketimestamp: building date/time info for local file --------------- */
  647.  
  648.   maketimestamp: procedure
  649.   parse arg dt tm
  650.   year  = substr(right(dt,8),7,2)
  651.   month = substr(right(dt,8),1,2)
  652.   day   = substr(right(dt,8),4,2)
  653.   hour  = substr(right(tm,6),1,2)
  654.   min   = substr(right(tm,6),4,2)
  655.   if (substr(right(tm,6),6,1) = "p" & hour \= 12) then hour = hour+12
  656.   if substr(month,1,1) = " " then month = "0"substr(month,2,1)
  657.   if substr(hour,1,1) = " " then hour = "0"substr(hour,2,1)
  658.   return "A"year||month||day||hour":"min
  659.  
  660. /* remotetimestamp : converting remote file date/time information ------- */
  661.  
  662.   remotetimestamp: procedure expose remote.
  663.   parse arg ind
  664.   rday = remote.ind.day
  665.   if length(rday) = 1 then rday = "0"rday
  666.   if substr(rday,1,1) = " " then rday = "0"substr(rday,2,1)
  667.   rmonth = remote.ind.month
  668.   if length(rmonth) = 1 then rmonth = "0"rmonth
  669.   if substr(rmonth,1,1) = " " then rmonth = "0"substr(rmonth,2,1)
  670.   rtime = remote.ind.time
  671.   if length(rtime) = 4 then rtime = "0"rtime
  672.   if substr(rtime,1,1) = " " then rtime = "0"substr(rtime,2,1)
  673.   return "A"substr(remote.ind.year,3)||rmonth||rday||rtime
  674.  
  675. /* wildcardmatch: returns 1 if matched, 0 if not ------------------------- */
  676.  
  677.   wildcardmatch: procedure
  678.   parse arg wildcard test
  679.   
  680.   if pos("*", substr(wildcard, pos("*", wildcard)+1)) \= 0 then 
  681.      do
  682.         say "two or more asterisks in wildcard string; terminating"
  683.         exit
  684.      end
  685.   
  686.   if substr(wildcard, pos("*",wildcard)+1, 1) = "?" then 
  687.      do
  688.         say "question mark right after asterisk in wildcard string; terminating"
  689.         exit
  690.      end
  691.   
  692.   it = 1
  693.   iw = 1
  694.   do forever
  695.      p = compare(substr(wildcard,iw), substr(test,it))
  696.      /*say "comparing["substr(wildcard,iw)"] and ["substr(test,it)"]; result is" p*/
  697.      if p = 0 then 
  698.         return 1
  699.      else do
  700.         w = substr(wildcard,iw+p-1,1)
  701.         if w = "?" then
  702.            do /* "?" encountered */
  703.               if substr(test,it,1) = "" then return 0
  704.               iw = iw + p
  705.               it = it + p
  706.               iterate
  707.            end
  708.         else if w = "*" then
  709.                 do /* "*" encountered */
  710.                    wp = substr(wildcard,iw+p)
  711.                    /*say "wp is ["wp"]"*/
  712.                    if wp = "" then return 1
  713.                    iw = iw + p
  714.                    nq = pos("?", wp)
  715.                    /*say "nq is" nq*/
  716.                    if nq = 0 then
  717.                       ss = wp
  718.                    else
  719.                       ss = substr(wp, 1, nq-1)
  720.                    /*say "searching ["ss"] in ["substr(test,it+p-1)"]"*/
  721.                    it = it + p + pos(ss, substr(test,it+p)) - 1
  722.                 end
  723.              else 
  724.                 do /* mismatch ! */
  725.                    /*say "mismatch"*/
  726.                    return 0
  727.                 end
  728.      end
  729.   end
  730.  
  731. /* Usage of rxmirror: command line options ------------------------------ */
  732.  
  733.   Usage: procedure
  734.   say
  735.   say "usage: rxmirror [ftp-server-host] [starting-directory] [userid] [password]"
  736.   say "   ftp-server-host    - hostname (not IP number) of ftp server"
  737.   say "   starting-directory - directory from which mirroring starts;"
  738.   say "                        rxmirror will recurse into subdirs"
  739.   say "   userid             - your login on remote server"
  740.   say "   password           - your password on the ftp server"
  741.   say
  742.   say "read rxmirror.doc for additional details"
  743.   say
  744.   return
  745.